package mac5789.projeto4.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

/**
 * Modelo de transições
 * 
 * @author lundberg
 * 
 */
public class TransitionModel {

	private final Random random = new Random(System.currentTimeMillis());

	private final Map<State, Double> transitionProbabilityMap = new HashMap<State, Double>();

	public static TransitionModelBuilder aTransitionModel() {
		return new TransitionModelBuilder();
	}

	/**
	 * Construtor
	 * 
	 * @param states estados
	 * @param probabilities probabilidades
	 */
	private TransitionModel(State[] states, Double[] probabilities) {
		if (states.length != probabilities.length) {
			throw new IllegalArgumentException("O número de estados e probabilidades deveria ser igual.");
		}
		double sum = 0.0;
		for (int i = 0; i < states.length; i++) {
			sum += probabilities[i];
			this.transitionProbabilityMap.put(states[i], probabilities[i]);
		}
		if (Math.abs(1 - sum) > 0.001) {
			throw new IllegalArgumentException("Soma das probabilidades deveria ser 1, dada " + sum);
		}
	}

	/**
	 * Conjunto de estados possíveis da transição
	 * 
	 * @return Set of State
	 */
	public Set<State> possibleStates() {
		return transitionProbabilityMap.keySet();
	}

	/**
	 * Probabilidade do estado ser o resultante da transição
	 * 
	 * @param state estado
	 * @return probabilidade, entre 0 e 1
	 */
	public double probability(State state) {
		if (transitionProbabilityMap.containsKey(state)) {
			return transitionProbabilityMap.get(state);
		} else {
			return 0.0;
		}
	}

	/**
	 * Escolhe de forma aleatória e uniforme, proporcional às probabilidades dos
	 * estados, um dos estados possíveis
	 * 
	 * @return estado
	 */
	public State pickState() {
		double sample = random.nextDouble();
		Iterator<State> it = this.possibleStates().iterator();
		while (it.hasNext()) {
			State state = it.next();
			if (!it.hasNext() || sample < this.probability(state)) {
				return state;
			} else {
				sample -= this.probability(state);
			}
		}
		throw new IllegalStateException("Não deveria chegar aqui.");
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((transitionProbabilityMap == null) ? 0 : transitionProbabilityMap.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		TransitionModel other = (TransitionModel) obj;
		if (transitionProbabilityMap == null) {
			if (other.transitionProbabilityMap != null) {
				return false;
			}
		} else if (!transitionProbabilityMap.equals(other.transitionProbabilityMap)) {
			return false;
		}
		return true;
	}

	@Override
	public String toString() {
		return String.format("TransitionModel:[%s]", transitionProbabilityMap);
	}

	static class TransitionModelBuilder {

		private List<State> states = new ArrayList<State>();

		private List<Double> probabilities = new ArrayList<Double>();

		public TransitionModelBuilder to(Object stateToken, double probability) {
			return this.with(new State(stateToken), probability);
		}

		public TransitionModelBuilder with(State state, double probability) {
			this.states.add(state);
			this.probabilities.add(probability);
			return this;
		}

		public TransitionModel build() {
			State[] stateArray = states.toArray(new State[states.size()]);
			Double[] probabilityArray = probabilities.toArray(new Double[probabilities.size()]);
			return new TransitionModel(stateArray, probabilityArray);
		}
	}

}
